package ch.ethz.fcl.metrobuzz.data.od;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import ch.ethz.fcl.metrobuzz.data.MBData;
import ch.ethz.fcl.metrobuzz.data.scene.basemap.Node;
import ch.ethz.fcl.metrobuzz.data.scene.spacetime.Trip.TripSegment;
import ch.ethz.fcl.metrobuzz.tools.MBTools;

/**
 * Origin-Destination Matrix
 * 
 * @author ZengWei
 * 
 */
public class ODMatrix {
	private MBData data;
	private SelectLinkQuery query = null;

	private Map<Node, Float>[] originOuts = null;
	private Map<Node, Float>[] destinIns = null;
	private Map<ODMatrixElement, Float>[] odElements = null;

	private final boolean DEBUG = false;

	public ODMatrix(MBData data, SelectLinkQuery query) {
		this.data = data;
		this.query = query;
	}

	@SuppressWarnings("unchecked")
	protected void reset() {
		odElements = (HashMap<ODMatrixElement, Float>[]) new HashMap<?, ?>[MBData.ONE_DAY_TIME
				- MBData.TRIP_STRAT_TIME];

		originOuts = (HashMap<Node, Float>[]) new HashMap<?, ?>[MBData.ONE_DAY_TIME
				- MBData.TRIP_STRAT_TIME];
		destinIns = (HashMap<Node, Float>[]) new HashMap<?, ?>[MBData.ONE_DAY_TIME
				- MBData.TRIP_STRAT_TIME];
	}

	@SuppressWarnings("unchecked")
	protected void generate() {
		for (int time = 0; time < MBData.ONE_DAY_TIME - MBData.TRIP_STRAT_TIME; time++) {
			List<TripSegment> curPassTrips = query.getAllPassTrips()[time];

			odElements[time] = new HashMap<ODMatrixElement, Float>();
			originOuts[time] = new HashMap<Node, Float>();
			destinIns[time] = new HashMap<Node, Float>();

			if (curPassTrips == null || curPassTrips.size() == 0)
				continue;

			for (int i = 0; i < curPassTrips.size(); i++) {
				TripSegment ts = curPassTrips.get(i);

				Node start = ts.getDepNode();
				Node end = ts.getArrNode();

				ODMatrixElement od = new ODMatrixElement(start, end);
				Float value = odElements[time].get(od);
				if (value == null) {
					value = 1F;
					od.setProbability(ts.getProbability());
				} else
					value += ts.getProbability();
				odElements[time].put(od, value);

				Float oValue = originOuts[time].get(start);
				if (oValue == null) {
					oValue = 1F;
				} else
					oValue += ts.getProbability();
				originOuts[time].put(start, oValue);

				Float dValue = destinIns[time].get(end);
				if (dValue == null) {
					dValue = 1F;
				} else
					dValue += ts.getProbability();
				destinIns[time].put(end, dValue);
			}

			odElements[time] = (Map<ODMatrixElement, Float>) MBTools
					.sortByComparator(odElements[time], false);
			originOuts[time] = (Map<Node, Float>) MBTools
					.sortByComparator(originOuts[time], false);
			destinIns[time] = (Map<Node, Float>) MBTools.sortByComparator(destinIns[time], false);

			if (DEBUG) {
				System.out.println("Origin Matrix in time " + time);
				printMap(originOuts[time], curPassTrips.size());
				//
				// System.out.println("End Matrix");
				// printMap(destinationMatrix, passTrips.size());

				// System.out.println("OD Matrix");
				// printMap(odMatrix, passTrips.size());

				// System.out.println("OD Matrix Distribution");
				// printMap(odMatrixDistri, odMatrixDistri.size());
			}
		}
	}

	private static <T> void printMap(Map<T, Float> map, int totalNum) {
		int count = 0;
		System.out.println(map.size() + " values ");
		for (Entry<T, Float> entry : map.entrySet()) {
			if (count < 100000) {
				System.out.println("Key : "
						+ entry.getKey().toString()
						+ " Value : "
						+ entry.getValue()
						+ " Percentage: "
						+ MBTools.numToPercentage(1.0f * entry.getValue()
								/ totalNum));
			} else
				return;
			count++;
		}
	}

	private Map<?, Float> getSubMatrix(Map<?, Float>[] allMatrix) {
		if (allMatrix == null)
			return null;

		if (MBData.period == 1)
			return allMatrix[MBData.startTime - MBData.TRIP_STRAT_TIME];

		Map<Object, Float> matrix = new HashMap<Object, Float>();
		for (int t = MBData.startTime - MBData.TRIP_STRAT_TIME; t < Math.min(
				MBData.startTime + MBData.period - MBData.TRIP_STRAT_TIME,
				MBData.ONE_DAY_TIME - MBData.TRIP_STRAT_TIME); t++) {
			Map<?, Float> curODMatrix = allMatrix[t];

			if (curODMatrix == null)
				continue;

			for (Object pair : curODMatrix.keySet()) {
				Float volume = matrix.get(pair);
				if (volume == null)
					matrix.put(pair, curODMatrix.get(pair));
				else
					matrix.put(pair, volume + curODMatrix.get(pair));
			}
		}

		return matrix;
	}

	public Map<ODMatrixElement, Float> getODMatrix() {
		@SuppressWarnings("unchecked")
		Map<ODMatrixElement, Float> subMatrix = (Map<ODMatrixElement, Float>) getSubMatrix(odElements);

		return subMatrix;
	}

	public Map<Node, Float> getOriginMatrix() {
		@SuppressWarnings("unchecked")
		Map<Node, Float> subMatrix = (Map<Node, Float>) getSubMatrix(originOuts);

		return subMatrix;
	}

	public Map<Node, Float> getDestinMatrix() {
		@SuppressWarnings("unchecked")
		Map<Node, Float> subMatrix = (Map<Node, Float>) getSubMatrix(destinIns);

		return subMatrix;
	}

	public Map<ODMatrixElement, Float>[] getAllODMatrix() {
		return odElements;
	}

	public Map<Node, Float>[] getAllOriginMatrix() {
		return originOuts;
	}

	public Map<Node, Float>[] getAllDestinMatrix() {
		return destinIns;
	}

	public MBData getData() {
		return data;
	}
}
